package com.hb.B_面向对象.E_密封类_代数类型

/**
 * 密封类是用来表示受限的类继承结构
 *
 * 什么是受限的类继承结构:
 *
 * 所谓受限的类继承结构，即当类中的一个值只能是有限的几种类型，而不能是其他的任何类型。
 * 这种受限的类继承结构从某种意义上讲，它相当于是枚举类的扩展。但是，我们知道Kotlin的枚举类中的枚举常量是受限的，因为每一个枚举常量只能存在一个实例。
 * 但是其和枚举类不同的地方在于，密封类的一个子类可以有可包含状态的多个实例。
 * 也可以说成，密封类是包含了一组受限的类集合，因为里面的类都是继承自这个密封类的。但是其和其他继承类（open）的区别在，密封类可以不被此文件外被继承，
 * 有效保护代码。但是，其密封类的子类的扩展是是可以在程序中任何位置的，即可以不在同一文件下。
 *
 * 定义密封类的关键字：sealed
 *
 * 声明格式:
 * sealed class SealedExpr()
 *
 * 密封类是不能被实例化的
 * val mSealedExpr = SealedExpr()  // 这段代码是错误的，编译器直接会报错不能编译通过。
 * 既然密封类是不能实例化，那么我们要怎么使用，或者说它的作用是什么呢？
 *
 * 作用:
 * 用来表示受限的类继承结构。
 *
 *
 * 和普通继承类的区别?
 * 普通的继承类使用open关键字定义，在项目中的类都可继承至该类。
 * 而密封类的子类必须是在密封类的内部或必须存在于密封类的同一文件。
 * 这一点就是上面提到的有效的代码保护
 *
 * 和枚举类的区别?
 * 枚举类的中的每一个枚举常量都只能存在一个实例。而密封类的子类可以存在多个实例。
 *
 * 使用密封类的好处
 * 有效的保护代码
 * 在使用when表达式 的时候，如果能够验证语句覆盖了所有情况，就不需要为该语句再添加一个else子句了。
 *
 */
fun main() {
    val mPerson1 = SealedExpr.Person("Java",22)
    println(mPerson1)

    val mPerson2 = SealedExpr.Person("Kotlin",23)
    println(mPerson2)

    //两个hashcode不一样
    println(mPerson1.hashCode())
    println(mPerson2.hashCode())

    println(SealedExpr.Add.add(1,2))

    eval(SealedExpr.Add)
}

sealed class SealedExpr(){
    data class Person(val name : String, val num2 : Int) : SealedExpr()
    object Add : SealedExpr()   // 单例模式
   companion object Minus : SealedExpr() // 单例模式

}
// 其子类可以定在密封类外部，但是必须在同一文件中。`v1.1`之前只能定义在密封类内部
//分析：即所定义的子类都必须继承于密封类，表示一组受限的类
object NotANumber : SealedExpr()

//子类的类扩展实例 相当于在Add类中添加了一个方法
fun  <T>SealedExpr.Add.add(num1 : T, num2 : T) : Int{
    return 100
}

fun eval(expr: SealedExpr) = when(expr){
    is SealedExpr.Add -> println("is Add")
    is SealedExpr.Minus -> println("is Minus")
    is SealedExpr.Person -> println(SealedExpr.Person("Koltin",22))
    NotANumber -> Double.NaN
}
/**
 *在实际的项目开发当中，数据类(data)类的用处是很多的，
 * 因为在开发APP时，往往会根据后台开发者所提供的接口返回的json而生成一个实体类，
 * 现在我们学习了数据类后，就不用再像Java一样写那么多代码了，即使是用编辑器提供的方法去自动生成。
 * 但是代码量上就能节省我们很多时间，并且也更加简洁。
 *
 * 密封类的情况在实际开发中不是很常见。只有当时特殊的需求会用到的时候，才会使用密封类。
 */


/**
 *
 */